<!DOCTYPE html> <!-- -*- html -*- -->
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1">
  <title>Function of Best Fit</title>
  <link rel="shortcut icon" href="img/gatech.gif"/>

  
      <link rel="stylesheet" href="css/demo.css?vers=2759ff">
  

  <style>
      

  #eqn-here {
    color: var(--palette-red);
  }
  #sumsq-here {
    color: var(--palette-violet);
  }

  </style>

</head>
<body>
    

    
        <script src="js/demo.js?vers=77646a"></script>
    

    <script type="text/javascript">
        "use strict";
        DomReady.ready(function() {

        var bestFitStr, bestfit, bvec, dot, func, funcStr, i, k, l, len, len1, letter, m, makeString, matrix, numParams, numTargets, obj, op, param, params, range, ref, ref1, ref2, size, solve, targets, units, updateCaption, vars, xhat, zeroParams;

range = urlParams.get('range', 'float', 10);

funcStr = (ref = urlParams.func) != null ? ref : 'C*x+D';

funcStr = funcStr.replace(/\s+/g, '+');

func = exprEval.Parser.parse(funcStr);

for (op in func.unaryOps) {
  if (op.match(/^[a-zA-Z]+$/)) {
    funcStr = funcStr.replace(new RegExp(op, 'g'), "\\" + op);
  }
}

params = [];

zeroParams = {};

vars = [];

ref1 = func.variables().sort();
for (k = 0, len = ref1.length; k < len; k++) {
  letter = ref1[k];
  if (letter === 'x' || letter === 'y') {
    vars.push(letter);
  } else {
    params.push(letter);
    zeroParams[letter] = 0;
  }
}

size = vars.length + 1;

numParams = params.length;

units = [];

for (i = l = 0, ref2 = numParams; 0 <= ref2 ? l < ref2 : l > ref2; i = 0 <= ref2 ? ++l : --l) {
  obj = {};
  for (m = 0, len1 = params.length; m < len1; m++) {
    param = params[m];
    obj[param] = 0;
  }
  obj[params[i]] = 1;
  units.push(obj);
}

targets = [];

i = 1;

while (urlParams["v" + i] != null) {
  targets.push(urlParams.get("v" + i, 'float[]'));
  i++;
}

numTargets = targets.length;

matrix = (function() {
  var n, ref3, results;
  results = [];
  for (n = 0, ref3 = numParams; 0 <= ref3 ? n < ref3 : n > ref3; 0 <= ref3 ? n++ : n--) {
    results.push((function() {
      var o, ref4, results1;
      results1 = [];
      for (o = 0, ref4 = numTargets; 0 <= ref4 ? o < ref4 : o > ref4; 0 <= ref4 ? o++ : o--) {
        results1.push(0);
      }
      return results1;
    })());
  }
  return results;
})();

bvec = (function() {
  var n, ref3, results;
  results = [];
  for (n = 0, ref3 = numTargets; 0 <= ref3 ? n < ref3 : n > ref3; 0 <= ref3 ? n++ : n--) {
    results.push(0);
  }
  return results;
})();

window.xhat = xhat = (function() {
  var n, ref3, results;
  results = [];
  for (n = 0, ref3 = numParams; 0 <= ref3 ? n < ref3 : n > ref3; 0 <= ref3 ? n++ : n--) {
    results.push(0);
  }
  return results;
})();

bestfit = function(x) {
  return 0;
};

bestFitStr = '';

dot = function(v1, v2) {
  var n, ref3, ret;
  ret = 0;
  for (i = n = 0, ref3 = v1.length; 0 <= ref3 ? n < ref3 : n > ref3; i = 0 <= ref3 ? ++n : --n) {
    ret += v1[i] * v2[i];
  }
  return ret;
};

updateCaption = function() {};

solve = function() {
  var ATA, ATb, constant, eqno, j, len2, len3, linear, n, o, p, q, ref3, ref4, solver, target, toEval;
  for (eqno = n = 0, ref3 = numTargets; 0 <= ref3 ? n < ref3 : n > ref3; eqno = 0 <= ref3 ? ++n : --n) {
    target = targets[eqno];
    toEval = {};
    for (i = o = 0, len2 = vars.length; o < len2; i = ++o) {
      letter = vars[i];
      toEval[letter] = target[i];
    }
    linear = func.simplify(toEval);
    constant = linear.evaluate(zeroParams);
    for (i = p = 0, ref4 = numParams; 0 <= ref4 ? p < ref4 : p > ref4; i = 0 <= ref4 ? ++p : --p) {
      matrix[i][eqno] = linear.evaluate(units[i]) - constant;
    }
    bvec[eqno] = target[size - 1] - constant;
  }
  ATA = (function() {
    var q, ref5, results;
    results = [];
    for (j = q = 0, ref5 = numParams; 0 <= ref5 ? q < ref5 : q > ref5; j = 0 <= ref5 ? ++q : --q) {
      results.push((function() {
        var r, ref6, results1;
        results1 = [];
        for (i = r = 0, ref6 = numParams; 0 <= ref6 ? r < ref6 : r > ref6; i = 0 <= ref6 ? ++r : --r) {
          results1.push(dot(matrix[i], matrix[j]));
        }
        return results1;
      })());
    }
    return results;
  })();
  ATb = (function() {
    var q, ref5, results;
    results = [];
    for (i = q = 0, ref5 = numParams; 0 <= ref5 ? q < ref5 : q > ref5; i = 0 <= ref5 ? ++q : --q) {
      results.push(dot(matrix[i], bvec));
    }
    return results;
  })();
  solver = rowReduce(ATA)[3];
  solver(ATb, xhat);
  toEval = {};
  for (i = q = 0, len3 = params.length; q < len3; i = ++q) {
    letter = params[i];
    toEval[letter] = xhat[i];
  }
  bestfit = func.simplify(toEval).toJSFunction(vars.join(','));
  makeString();
  return updateCaption();
};

makeString = function() {
  var len2, n, results, val, valAlone, valMinus, valPlus;
  bestFitStr = funcStr;
  results = [];
  for (i = n = 0, len2 = params.length; n < len2; i = ++n) {
    letter = params[i];
    val = xhat[i];
    if (val >= 0) {
      valAlone = val.toFixed(2);
      valPlus = "+" + valAlone;
      valMinus = "-" + valAlone;
    }
    if (val < 0) {
      val = -val;
      valAlone = val.toFixed(2);
      valPlus = "-" + valAlone;
      valMinus = "+" + valAlone;
      valAlone = "-" + valAlone;
    }
    bestFitStr = bestFitStr.replace(new RegExp("\\+" + letter + "\\*", 'g'), valPlus + '\\,');
    bestFitStr = bestFitStr.replace(new RegExp("\\+" + letter, 'g'), valPlus);
    bestFitStr = bestFitStr.replace(new RegExp("-" + letter + "\\*", 'g'), valMinus + '\\,');
    bestFitStr = bestFitStr.replace(new RegExp("-" + letter, 'g'), valMinus);
    bestFitStr = bestFitStr.replace(new RegExp(letter + "\\*", 'g'), valAlone + '\\,');
    bestFitStr = bestFitStr.replace(new RegExp("" + letter, 'g'), valAlone);
    results.push(bestFitStr = bestFitStr.replace(/\*/g, ''));
  }
  return results;
};

solve();

window.demo = new (size === 2 ? Demo2D : Demo)({}, function() {
  var bestFitElt, clipCube, minimElt, str, varsStr, view;
  window.mathbox = this.mathbox;
  view = this.view({
    axes: true,
    grid: true,
    viewRange: [[-range, range], [-range, range], [-range, range]].slice(0, size)
  });
  this.labeledPoints(view, {
    name: 'targets',
    points: targets,
    colors: (function() {
      var n, ref3, results;
      results = [];
      for (n = 0, ref3 = numTargets; 0 <= ref3 ? n < ref3 : n > ref3; 0 <= ref3 ? n++ : n--) {
        results.push(new Color("blue"));
      }
      return results;
    })(),
    live: true,
    pointOpts: {
      zIndex: 2
    }
  });
  if (size === 2) {
    view.interval({
      channels: 2,
      range: [-range, range],
      width: 100,
      expr: function(emit, x) {
        return emit(x, bestfit(x));
      }
    }).line({
      color: new Color("red").arr(),
      width: 4,
      zIndex: 1
    });
  }
  if (size === 3) {
    clipCube = this.clipCube(view, {
      draw: true
    });
    clipCube.clipped.area({
      channels: 3,
      rangeX: [-range, range],
      rangeY: [-range, range],
      width: 100,
      height: 100,
      expr: function(emit, x, y) {
        return emit(x, y, bestfit(x, y));
      }
    }).surface({
      color: new Color("red").arr(),
      opacity: .25,
      zIndex: 1,
      fill: true,
      lineX: false,
      lineY: false
    }).resample({
      shader: null,
      size: 'relative',
      width: 1 / 10,
      height: 1 / 10
    }).surface({
      color: new Color("red").arr(),
      opacity: .6,
      zIndex: 1,
      zBias: 2,
      fill: false,
      lineX: true,
      lineY: true
    });
  }
  view.array({
    channels: size,
    width: 2,
    items: numTargets,
    expr: function(emit, end) {
      var n, ref3, results, x, y, z;
      results = [];
      for (i = n = 0, ref3 = numTargets; 0 <= ref3 ? n < ref3 : n > ref3; i = 0 <= ref3 ? ++n : --n) {
        x = targets[i][0];
        y = targets[i][1];
        if (size === 2) {
          if (end) {
            emit(x, bestfit(x));
          } else {
            emit(x, y);
          }
        }
        if (size === 3) {
          z = targets[i][2];
          if (end) {
            results.push(emit(x, y, bestfit(x, y)));
          } else {
            results.push(emit(x, y, z));
          }
        } else {
          results.push(void 0);
        }
      }
      return results;
    }
  }).line({
    color: new Color("violet").arr(),
    width: 3,
    zIndex: 3
  });
  this.draggable(view, {
    points: targets,
    postDrag: solve
  });
  str = '<p>Best-fit equation: <span id="eqn-here"></span></p>';
  str += '<p>Quantity minimized: <span id="sumsq-here"></span></p>';
  this.caption(str);
  bestFitElt = document.getElementById('eqn-here');
  minimElt = document.getElementById('sumsq-here');
  varsStr = vars.join(',');
  updateCaption = (function(_this) {
    return function() {
      var diff, len2, minimized, n, quantity, target;
      katex.render("\\quad f(" + varsStr + ") = " + bestFitStr, bestFitElt);
      minimized = [];
      quantity = 0;
      for (n = 0, len2 = targets.length; n < len2; n++) {
        target = targets[n];
        if (size === 2) {
          diff = target[1] - bestfit(target[0]);
        }
        if (size === 3) {
          diff = target[2] - bestfit(target[0], target[1]);
        }
        minimized.push((diff.toFixed(2)) + "^2");
        quantity += diff * diff;
      }
      str = '\\quad' + quantity.toFixed(2) + '=' + minimized.join('+');
      return katex.render(str, minimElt);
    };
  })(this);
  return updateCaption();
});


        });
    </script>
</body>
</html>

